﻿using System.Collections.Generic;
using System.Linq;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Mathematics;
using FreeGoo.GameEngine.Renderables.Links;

namespace FreeGoo.GameEngine.Renderables
{
    public abstract class Linkable : BodyRenderable, ILinkable
    {
        protected const float DefaultMinAllowedLinkDistance = 1.2f;
        protected const float DefaultLinkableDistanceLinkBias = 0.15f;
        protected const float DefaultBalloonLinkableDistanceLinkBias = 0.75f;

        protected Linkable(Geom geom)
            : base(geom)
        {
            Links = new List<Link>();
            LinkSoftnessFactor = 1;
            LinkBreakpointFactor = 2;
            LinkBiasFactor = 1;
            MinAllowedLinkDistance = DefaultMinAllowedLinkDistance;
        }

        public IList<Link> Links { get; private set; }
        public float LinkSoftnessFactor { get; set; }
        public float LinkBreakpointFactor { get; set; }
        public float LinkBiasFactor { get; set; }
        public float MinAllowedLinkDistance { get; set; }
        public float LastTestDistance { get; set; }
        public abstract float Radius { get; set; }
        public bool IsLinked { get { return Links.Count > 0; } }
        public Vector2 BodyLinkAnchor { get { return Vector2.Zero; } }
        public bool IsLinkedTo(ILinkable obj)
        {
            foreach (Link link in Links)
            {
                if (link.Linkable1 == obj || link.Linkable2 == obj)
                {
                    return true;
                }
            }

            return false;
        }

        public bool ContainsLink(Link link)
        {
            return Links.Contains(link);
        }

        public virtual bool AllowsLinkFrom(ILinkable linkable)
        {
            return Links.Count != 0;
        }

        public override void AddToEngine(Engine engine)
        {
            base.AddToEngine(engine);
            engine.Linkables.Add(this);
        }

        public override void Delete()
        {
            base.Delete();
            Engine.Linkables.Remove(this);
            ClearLinks();
        }

        public Link CreateLinkTo(ILinkable linkable)
        {
            return CreateLinkBetween(this, linkable);
        }

        public virtual Link CreateLinkBetween(ILinkable first, ILinkable second)
        {
            Link link = CreateActualLink(first, second);

            link.Joint.Softness *= LinkSoftnessFactor;
            link.Joint.Breakpoint *= LinkBreakpointFactor;
            link.Joint.BiasFactor *= LinkBiasFactor;
            return link;
        }

        public virtual void AddLink(Link link)
        {
            Links.Add(link);
        }

        public virtual void RemoveLink(Link link)
        {
            Links.Remove(link);
        }

        public virtual void ClearLinks()
        {
            while (Links.Count > 0)
            {
                Links[0].Delete();
            }
        }

        public virtual bool AllowsLinkTo(ILinkable linkable)
        {
            return
                linkable != this &&
                linkable.AllowsLinkFrom(this);
        }

        protected virtual Link CreateActualLink(ILinkable first, ILinkable second)
        {
            return MixedFactory.CreatePinJointLinkBetweenLinkableObjects(Engine, first, second, this);
        }
    }
}